package org.bdware.bdoa.authentication.utils;


import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.bdware.bdoa.authentication.JwtConfig;
import org.bdware.bdoa.authentication.exception.JwxException;
import org.bdware.bdoa.authentication.service.JwtService;
import org.bdware.bdoa.authentication.serviceImpl.JwsServiceImpl;

import java.text.ParseException;
import java.util.Date;

public class JwxUtils {

    public static String DEFAULT_TOKEN_NAME = "Authorization";
    public static String PAYLOAD_SIGN_ERROR = "PAYLOAD_SIGN_ERROR";
    public static String TOKEN_VERIFY_ERROR = "TOKEN_VERIFY_ERROR";
    public static String KEY_GEN_ERROR = "KEY_GEN_ERROR";
    public static String CLAIM_PAYLOAD = "payload";

    public static JwxUtils jwxUtils;



    //key为jwk格式的私钥，source为要加密的字符
    public static String encrypt(String source, String key){
        String result = null;
        try {
            JWK jwKeyPair = JWK.parse(key);
            result = CertUtils.Sign(source.getBytes(), jwKeyPair);
        } catch (ParseException e) {
            e.printStackTrace();
        } finally {
            return result;
        }
    }

    //TODO need test
    public static String decrypt(String source, String key){
        String result = null;
        try {
            JWK jwKeyPair = JWK.parse(key);
            byte[] tmp = CertUtils.decrypt(source.getBytes(), jwKeyPair);
            result = new String(tmp);
        } catch (ParseException e) {
            e.printStackTrace();
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return result;
    }


    //验证
    public  boolean verify(String source, String encrypted, String publicKeyStr){
        boolean verified = false;
        try {

            JWK publicKey = JWK.parse(publicKeyStr);
            JWSObject verify =  JWSObject.parse(encrypted);
            JWSVerifier verifier = null;
            if(publicKey.getKeyType() == KeyType.RSA){
                verifier = new RSASSAVerifier(publicKey.toRSAKey().toRSAPublicKey());
            }else if(publicKey.getKeyType() == KeyType.EC){
                verifier = new ECDSAVerifier(publicKey.toECKey().toECPublicKey());
            }else{
                System.out.println("unsupported Algorithm");
            }
            verified = verify.verify(verifier);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return verified;
        }
    }

    //验证
    public static boolean verify(String source, String encrypted, JWK publicKey){
        try {
            return CertUtils.verify(source.getBytes(), encrypted, publicKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 使用 HMAC 算法签名信息（Payload 中只包含私有信息）
     *
     * @param info
     * @param key
     * @return
     */
    public static String signDirectByHMAC(String info, String key) {
        try {
            JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
                    .type(JOSEObjectType.JWT)
                    .build();

            // 建立一个载荷 Payload
            Payload payload = new Payload(info);

            // 将头部和载荷结合在一起
            JWSObject jwsObject = new JWSObject(jwsHeader, payload);

            // 建立一个密匙
            JWSSigner jwsSigner = new MACSigner(key);

            // 签名
            jwsObject.sign(jwsSigner);

            // 生成 token
            return jwsObject.serialize();
        } catch (JOSEException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.PAYLOAD_SIGN_ERROR, e);
        }
    }

    /**
     * 使用 RSA 算法签名信息（Payload 中只包含私有信息）
     *
     * @param info
     * @param rsaKey
     * @return
     */
    public static String signDirectByRSA(String info, RSAKey rsaKey) {
        try {
            JWSSigner signer = new RSASSASigner(rsaKey);
            JWSObject jwsObject = new JWSObject(
                    new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaKey.getKeyID()).build(),
                    new Payload(info)
            );
            // 进行加密
            jwsObject.sign(signer);

            return jwsObject.serialize();
        } catch (JOSEException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.PAYLOAD_SIGN_ERROR, e);
        }
    }

    /**
     * 使用 HMAC 算法验证 token（Payload 中只包含私有信息）
     *
     * @param token
     * @param key
     * @return
     */
    public static String verifyDirectByHMAC(String token, String key) {
        try {
            JWSObject jwsObject = JWSObject.parse(token);
            // 建立一个解锁密匙
            JWSVerifier jwsVerifier = new MACVerifier(key);
            if (jwsObject.verify(jwsVerifier)) {
                return jwsObject.getPayload().toString();
            }
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR, new NullPointerException("Payload can not be null"));
        } catch (JOSEException | ParseException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR, e);
        }
    }

    /**
     * 使用 RSA 算法验证 token（Payload 中只包含私有信息）
     *
     * @param token
     * @param rsaKey
     * @return
     */
    public static String verifyDirectByRSA(String token, RSAKey rsaKey) {
        try {
            RSAKey publicRSAKey = rsaKey.toPublicJWK();
            JWSObject jwsObject = JWSObject.parse(token);
            JWSVerifier jwsVerifier = new RSASSAVerifier(publicRSAKey);
            // 验证数据
            if (jwsObject.verify(jwsVerifier)) {
                return jwsObject.getPayload().toString();
            }
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR, new NullPointerException("Payload can not be null"));
        } catch (JOSEException | ParseException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR, e);
        }
    }

    /**
     * 使用 HMAC 算法签名信息（Payload 中只包含标准信息与私有信息）
     *
     * @param info
     * @param key
     * @param jwtConfig
     * @return
     */
    public static String signClaimByHMAC(String info, String key, JwtConfig jwtConfig) {
        try {
            JWSSigner jwsSigner = new MACSigner(key);
            final JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
            SignedJWT signedJWT = new SignedJWT(header, buildClaim(info, jwtConfig));
            signedJWT.sign(jwsSigner);
            return signedJWT.serialize();
        } catch (JOSEException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.PAYLOAD_SIGN_ERROR, e);
        }
    }

    /**
     * 使用 RSA 算法签名信息（Payload 中只包含标准信息与私有信息）
     *
     * @param info
     * @param rsaKey
     * @param jwtConfig
     * @return
     */
    public static String signClaimByRSA(String info, RSAKey rsaKey, JwtConfig jwtConfig) {
        try {
            JWSSigner jwsSigner = new RSASSASigner(rsaKey);
            final JWSHeader header = new JWSHeader(JWSAlgorithm.RS256);
            SignedJWT signedJWT = new SignedJWT(header, buildClaim(info, jwtConfig));
            signedJWT.sign(jwsSigner);
            return signedJWT.serialize();
        } catch (JOSEException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.PAYLOAD_SIGN_ERROR, e);
        }
    }

    private static JWTClaimsSet buildClaim(String info, JwtConfig jwtConfig) {

        JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
        String subject = jwtConfig.getSubject();
        if (!JwxUtils.isEmpty(subject)) {
            builder.subject(subject);
        }

        String issuer = jwtConfig.getIssuer();
        if (!JwxUtils.isEmpty(issuer)) {
            builder.issuer(issuer);
        }

        Date now = new Date();
        builder.issueTime(now);

        long expiredIn = jwtConfig.getExpiredIn();
        if (expiredIn > 0) {
            Date expiredAt = new Date(new Date().getTime() + expiredIn * 1000);
            builder.expirationTime(expiredAt);
        }

        long notBeforeIn = jwtConfig.getNotBeforeIn();
        if (notBeforeIn > 0) {
            builder.notBeforeTime(new Date(now.getTime() + notBeforeIn * 1000));
        }

        builder.claim(JwxUtils.CLAIM_PAYLOAD, info);

        // 生成 token
        JWTClaimsSet claimsSet = builder.build();

        return claimsSet;
    }

    /**
     * 使用 HMAC 算法验证 token（Payload 中只包含标准信息与私有信息）
     *
     * @param token
     * @param key
     * @param jwtConfig
     * @return
     */
    public static String verifyClaimByHMAC(String token, String key, JwtConfig jwtConfig) {
        try {
            final SignedJWT parseJWT = SignedJWT.parse(token);
            JWSVerifier jwsVerifier = new MACVerifier(key);

            return verifyClaim(jwsVerifier, parseJWT, jwtConfig);
        } catch (JOSEException | ParseException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR, e);
        }
    }

    /**
     * 使用 RSA 算法验证 token（Payload 中只包含标准信息与私有信息）
     *
     * @param token
     * @param rsaKey
     * @param jwtConfig
     * @return
     */
    public static String verifyClaimByRSA(String token, RSAKey rsaKey, JwtConfig jwtConfig) {
        try {
            final SignedJWT parseJWT = SignedJWT.parse(token);
            RSAKey publicRSAKey = rsaKey.toPublicJWK();
            JWSVerifier jwsVerifier = new RSASSAVerifier(publicRSAKey);

            return verifyClaim(jwsVerifier, parseJWT, jwtConfig);
        } catch (JOSEException | ParseException e) {
            e.printStackTrace();
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR, e);
        }
    }

    private static String verifyClaim(JWSVerifier jwsVerifier, SignedJWT parseJWT, JwtConfig jwtConfig)
            throws JOSEException, ParseException {
        final boolean verify = parseJWT.verify(jwsVerifier);

        if (!verify) {
            throw new JwxException("Token verify fail");
        }

        final JWTClaimsSet jwtClaimsSet = parseJWT.getJWTClaimsSet();

        if (jwtClaimsSet == null) {
            throw new JwxException("No content found");
        }

        Date expiredTime = jwtClaimsSet.getExpirationTime();

        long now = new Date().getTime();

        if (expiredTime != null && expiredTime.getTime() < now) {
            throw new JwxException("Token is expired");
        }

        Date notBeforeTime = jwtClaimsSet.getNotBeforeTime();

        if (notBeforeTime != null && now < notBeforeTime.getTime()) {
            throw new JwxException("Token has not taken effect now");
        }

        String payload = jwtClaimsSet.getStringClaim(JwxUtils.CLAIM_PAYLOAD);

        if (JwxUtils.isEmpty(payload)) {
            throw new JwxException(JwxUtils.TOKEN_VERIFY_ERROR,
                    new NullPointerException("Payload can not be null"));
        }

        return payload;
    }

    /**
     * 该方法为减少外部库/框架依赖而写，其他地方请不要引用
     *
     * @param src
     * @return
     */
    public static boolean isEmpty(String src) {
        return src == null || "".equals(src.trim());
    }

    /**
     * 获取内置 JwtService 的工厂方法。
     *
     * 优先采用 HMAC 算法实现
     *
     * @param jwtConfig
     * @return
     */
    public static JwtService obtainJwtService(JwtConfig jwtConfig) {
        //TODO @liuyi 当前直接通过jws进行验证的，未使用jwt，后面扩展一下
        return new JwsServiceImpl(jwtConfig);
//        if (!JwxUtils.isEmpty(jwtConfig.getHmacKey())) {
//            return new HMACJwtServiceImpl(jwtConfig);
//        }
//        return new RSAJwtServiceImpl(jwtConfig);
    }


}
